"""
认证服务层
处理用户认证、权限验证等业务逻辑
"""

from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, or_, func
from sqlalchemy.orm import selectinload
from fastapi import HTTPException, status

from app.models.auth import User, Role, Permission, UserRole, UserSession, RolePermission
from app.schemas.auth import (
    LoginRequest, RegisterRequest, UserCreate, UserUpdate, 
    ChangePasswordRequest, UserResponse, LoginResponse, UserProfile
)
from app.core.security import (
    verify_password, get_password_hash, create_access_token, create_refresh_token,
    verify_token, generate_session_token, check_user_locked, should_lock_user,
    calculate_lock_duration, get_client_ip
)
from app.core.config import settings
from app.core.logger import logger
from app.utils.timezone_utils import get_shanghai_now


class AuthService:
    """认证服务类"""
    
    def __init__(self):
        pass
    
    async def authenticate_user(
        self, 
        db: AsyncSession, 
        username: str, 
        password: str,
        request = None
    ) -> Optional[User]:
        """用户认证"""
        try:
            # 查找用户（支持用户名或邮箱登录）
            stmt = select(User).where(
                or_(User.username == username, User.email == username)
            ).options(
                selectinload(User.user_roles).selectinload(UserRole.role)
            )
            result = await db.execute(stmt)
            user = result.scalar_one_or_none()
            
            if not user:
                logger.warning(f"用户认证失败：用户不存在 - {username}")
                return None
            
            # 检查用户是否被锁定
            if check_user_locked(user):
                logger.warning(f"用户认证失败：账户被锁定 - {username}")
                raise HTTPException(
                    status_code=status.HTTP_423_LOCKED,
                    detail=f"账户已被锁定，请在 {user.locked_until} 后重试"
                )
            
            # 检查用户是否激活
            if not user.is_active:
                logger.warning(f"用户认证失败：账户未激活 - {username}")
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="账户未激活"
                )
            
            # 验证密码
            if not verify_password(password, user.password_hash):
                # 增加登录失败次数
                user.login_attempts += 1
                
                # 检查是否需要锁定账户
                if should_lock_user(user.login_attempts):
                    lock_duration = calculate_lock_duration(user.login_attempts)
                    user.locked_until = get_shanghai_now() + lock_duration
                    logger.warning(f"用户账户被锁定：{username}，锁定时长：{lock_duration}")
                
                await db.commit()
                logger.warning(f"用户认证失败：密码错误 - {username}")
                return None
            
            # 认证成功，重置登录失败次数
            user.login_attempts = 0
            user.locked_until = None
            user.last_login = get_shanghai_now()
            
            await db.commit()
            logger.info(f"用户认证成功：{username}")
            return user
            
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"用户认证异常：{str(e)}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="认证服务异常"
            )
    
    async def login(
        self, 
        db: AsyncSession, 
        login_data: LoginRequest,
        request = None
    ) -> LoginResponse:
        """用户登录"""
        user = await self.authenticate_user(
            db, login_data.username, login_data.password, request
        )
        
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="用户名或密码错误"
            )
        
        # 创建令牌
        token_data = {"sub": str(user.id), "username": user.username}
        
        # 根据"记住我"设置令牌过期时间
        if login_data.remember_me:
            access_expires = timedelta(days=settings.REMEMBER_ME_EXPIRE_DAYS)
            refresh_expires = timedelta(days=settings.REMEMBER_ME_EXPIRE_DAYS)
        else:
            access_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
            refresh_expires = timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        
        access_token = create_access_token(token_data, access_expires)
        refresh_token = create_refresh_token(token_data, refresh_expires)
        
        # 创建会话记录
        session_token = generate_session_token()
        session = UserSession(
            user_id=user.id,
            session_token=session_token,
            refresh_token=refresh_token,
            ip_address=get_client_ip(request) if request else None,
            user_agent=request.headers.get("User-Agent") if request else None,
            expires_at=get_shanghai_now() + access_expires
        )
        db.add(session)
        await db.commit()
        
        # 重新查询用户以获取完整的关系数据
        from sqlalchemy import select
        from sqlalchemy.orm import selectinload

        stmt = select(User).where(User.id == user.id).options(
            selectinload(User.user_roles).selectinload(UserRole.role).selectinload(Role.role_permissions).selectinload(RolePermission.permission)
        )
        result = await db.execute(stmt)
        user_with_relations = result.scalar_one()

        # 构建用户资料 - 使用UserProfile的@root_validator
        user_profile = UserProfile.from_orm(user_with_relations)
        
        return LoginResponse(
            access_token=access_token,
            refresh_token=refresh_token,
            expires_in=int(access_expires.total_seconds()),
            user=user_profile
        )
    
    async def register(
        self, 
        db: AsyncSession, 
        register_data: RegisterRequest
    ) -> UserResponse:
        """用户注册"""
        try:
            # 检查用户名是否已存在
            stmt = select(User).where(User.username == register_data.username)
            result = await db.execute(stmt)
            if result.scalar_one_or_none():
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="用户名已存在"
                )
            
            # 检查邮箱是否已存在
            stmt = select(User).where(User.email == register_data.email)
            result = await db.execute(stmt)
            if result.scalar_one_or_none():
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="邮箱已被注册"
                )
            
            # 创建用户
            user = User(
                username=register_data.username,
                email=register_data.email,
                password_hash=get_password_hash(register_data.password),
                full_name=register_data.full_name,
                phone=register_data.phone,
                department=register_data.department,
                position=register_data.position,
                password_changed_at=get_shanghai_now()
            )
            
            db.add(user)
            await db.flush()  # 获取用户ID
            
            # 分配默认角色（查看者）
            if register_data.role_ids:
                role_ids = register_data.role_ids
            else:
                # 获取默认角色
                stmt = select(Role).where(Role.name == "viewer")
                result = await db.execute(stmt)
                default_role = result.scalar_one_or_none()
                role_ids = [default_role.id] if default_role else []
            
            # 分配角色
            for role_id in role_ids:
                user_role = UserRole(
                    user_id=user.id,
                    role_id=role_id,
                    assigned_at=get_shanghai_now()
                )
                db.add(user_role)
            
            await db.commit()
            
            # 重新查询用户以获取完整信息，预加载所有必要的关系
            stmt = select(User).where(User.id == user.id).options(
                selectinload(User.user_roles).selectinload(UserRole.role).selectinload(Role.role_permissions).selectinload(RolePermission.permission)
            )
            result = await db.execute(stmt)
            user = result.scalar_one()
            
            logger.info(f"用户注册成功：{user.username}")
            return UserResponse.from_orm(user)
            
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"用户注册异常：{str(e)}")
            await db.rollback()
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="注册服务异常"
            )
    
    async def get_current_user(self, db: AsyncSession, token: str) -> User:
        """获取当前用户"""
        try:
            payload = verify_token(token, "access")
            if not payload:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="无效的访问令牌"
                )
            
            user_id = payload.get("sub")
            if not user_id:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="令牌格式错误"
                )
            
            stmt = select(User).where(User.id == int(user_id)).options(
                selectinload(User.user_roles).selectinload(UserRole.role).selectinload(Role.role_permissions).selectinload(RolePermission.permission)
            )
            result = await db.execute(stmt)
            user = result.scalar_one_or_none()
            
            if not user:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="用户不存在"
                )
            
            if not user.is_active:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="账户未激活"
                )
            
            return user
            
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"获取当前用户异常：{str(e)}")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="认证失败"
            )
    
    async def refresh_token(self, db: AsyncSession, refresh_token: str) -> Dict[str, Any]:
        """刷新访问令牌"""
        try:
            payload = verify_token(refresh_token, "refresh")
            if not payload:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="无效的刷新令牌"
                )
            
            user_id = payload.get("sub")
            username = payload.get("username")
            
            # 验证用户是否存在且激活
            stmt = select(User).where(User.id == int(user_id))
            result = await db.execute(stmt)
            user = result.scalar_one_or_none()
            
            if not user or not user.is_active:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="用户不存在或未激活"
                )
            
            # 创建新的访问令牌
            token_data = {"sub": user_id, "username": username}
            access_token = create_access_token(token_data)
            
            return {
                "access_token": access_token,
                "token_type": "bearer",
                "expires_in": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
            }
            
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"刷新令牌异常：{str(e)}")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="令牌刷新失败"
            )
    
    async def logout(self, db: AsyncSession, user: User, token: str):
        """用户登出"""
        try:
            # 使会话失效
            stmt = select(UserSession).where(
                and_(
                    UserSession.user_id == user.id,
                    UserSession.is_active == True
                )
            )
            result = await db.execute(stmt)
            sessions = result.scalars().all()
            
            for session in sessions:
                session.is_active = False
            
            await db.commit()
            logger.info(f"用户登出成功：{user.username}")
            
        except Exception as e:
            logger.error(f"用户登出异常：{str(e)}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="登出服务异常"
            )
